﻿/***********************************************************************************
 * 文 件 名   : system.cpp
 * 负 责 人   : 卢美宏
 * 创建日期   : 2018年4月1日
 * 文件描述   : 系统性的功能函数封装
 * 版权说明   : Copyright (c) 2008-2018   xx xx xx xx 技术有限公司
 * 其    他   :
 * 修改日志   :
***********************************************************************************/
#include <system.h>
#include <log.h>
#ifdef WIN32
#include <sys/timeb.h>
#else

#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#endif
namespace mdk
{
#define STDOUT_FILENOED 2
#define PATH_NAME_MAX_LEN 256
#define TIME_FROMAT_BUFFER_LEN 32
#ifdef __cplusplus
    extern "C"
    {
#endif
        /*****************************************************************************
         * 函 数 名  : GetCurrentMillisecs
         * 负 责 人  : 卢美宏
         * 创建日期  : 2018年4月1日
         * 函数功能  : 获取系统谁时间，毫秒级
         * 输入参数  : 无
         * 输出参数  : 无
         * 返 回 值  : 秒级时间整数
         * 调用关系  :
         * 其    它  :

        *****************************************************************************/
        UINT64 GetCurrentMillisecs()
        {
            UINT64 ret = 0;
#ifdef WIN32
            _timeb timebuffer;
            _ftime(&timebuffer);
            ret = timebuffer.time;
            ret = ret * 1000 + timebuffer.millitm;
            return ret;
#else
        timeval tv;
        ::gettimeofday(&tv, 0);
        ret = tv.tv_sec;
        return ret * 1000 + tv.tv_usec / 1000;
#endif
        }

        /*****************************************************************************
         * 函 数 名  : getCurrentTime
         * 负 责 人  : 卢美宏
         * 创建日期  : 2021年9月28日
         * 函数功能  :  获取时间格式化后字符串
         * 输入参数  : buffer 缓存地址
         *            len 缓存大小
         * 输出参数  : 无
         * 返 回 值  : 执行结果
         * 调用关系  :
         * 其    它  :

        *****************************************************************************/

        char *getCurrentTime(char *buffer, int len)
        {
            struct tm local;  //定义tm结构指针存储时间信息
            time_t now;       //声明time_t类型变量
            now = time(NULL); //获取当前系统的日历时间
#ifdef WIN32
            timeb tb;
            ftime(&tb); //微秒

            localtime_s(&local, &now); //localtime()函数是将日历时间转化为本地时间
            snprintf(buffer, len,
                     "%4d-%02d-%02d %02d:%02d:%02d:%03d",
                     local.tm_year + 1900,
                     local.tm_mon + 1, local.tm_mday,
                     local.tm_hour, local.tm_min,
                     local.tm_sec, tb.millitm);
#else
        struct timeval curTime;

        gettimeofday(&curTime, 0);
        localtime_r(&now, &local);
        snprintf(buffer, len,
                 "%4d-%02d-%02d %02d:%02d:%02d:%03d",
                 local.tm_year + 1900,
                 local.tm_mon + 1, local.tm_mday,
                 local.tm_hour, local.tm_min,
                 local.tm_sec, (int)curTime.tv_usec / 1000);
#endif
            return buffer;
        }
#ifndef WIN32
        /*****************************************************************************
         * 函 数 名  : OS_SafeSystem
         * 负 责 人  : 卢美宏
         * 创建日期  : 2018年4月1日
         * 函数功能  : 带超时机制的执行脚本命令
         * 输入参数  : char *pcCmd       待执行命令
                     char *argv[]      执行命令时需要的参数
                    UINT uiTimeOut     指定命令最长执行时间
                    STATUS *piScriptRet  命令执行退出结果
        * 输出参数  : 无
        * 返 回 值  :
        * 调用关系  :
        * 其    它  :

        *****************************************************************************/
        STATUS OS_SafeSystem(char *pcCmd, char *argv[], UINT uiTimeOut, STATUS *piScriptRet)
        {
            return OS_SafeSystemSub(pcCmd, argv, uiTimeOut, piScriptRet, NULL);
        }

        /*****************************************************************************
         * 函 数 名  : OS_SafeSystemSub
         * 负 责 人  : 卢美宏
         * 创建日期  : 2018年6月16日
         * 函数功能  : 带超时机制的执行脚本命令，同时可以获知子进程的pid
         * 输入参数  : 
         *            char *pcCmd        待执行命令
         *            char *argv[]       命令执行参数
         *            UINT uiTimeOut      命令执行超时时间
         *            STATUS *piScriptRet   命令自行退出状态
         *            pid_t *ptChildPid  子线程id
        * 输出参数  : 无
        * 返 回 值  : 执行结果
        * 调用关系  :
        * 其    它  :

        *****************************************************************************/
        STATUS OS_SafeSystemSub(char *pcCmd, char *argv[], UINT uiTimeOut,
                                STATUS *piScriptRet, pid_t *ptChildPid)
        {
            STATUS iRet = RET_ERR;
            pid_t tChildPid = 0;
            struct rlimit stFileLimit;
            UINT uiFileIndex = 0;
            STATUS iScriptRet = RET_ERR;

            if (NULL == pcCmd)
                return RET_ERR;

            tChildPid = vfork();
            if (0 > tChildPid)
            {
                LOG_ERROR("Creat Child fail.");
                return RET_ERR;
            }

            if (ptChildPid != NULL)
                *ptChildPid = tChildPid;

            if (tChildPid == 0)
            {
                //恢复三种信号源
                (void)signal(SIGQUIT, SIG_DFL);
                (void)signal(SIGCHLD, SIG_DFL);
                (void)signal(SIGINT, SIG_DFL);
                setpgid(0, 0); //设置组便于一次kill
                //获取进程能打开最大文件数并关闭
                if (0 == getrlimit(RLIMIT_NOFILE, &stFileLimit))
                {
                    for (uiFileIndex = STDOUT_FILENOED + 1;
                         uiFileIndex < stFileLimit.rlim_max;
                         ++uiFileIndex)
                    {
                        close((int32_t)uiFileIndex);
                    }
                    if (NULL == argv)
                    {
                        (void)execl("/bin/sh", "sh", "-c", pcCmd, (char *)0);
                    }
                    else
                    {
                        (void)execv(pcCmd, argv);
                    }
                }
                _exit(127);
            }
            else
            {
                iRet = OS_WaitChild(tChildPid, 0, uiTimeOut, &iScriptRet, NULL, 0);

                if (piScriptRet != NULL)
                    *piScriptRet = iScriptRet;

                if ((RET_OK != iRet) || (RET_OK != iScriptRet))
                {
                    LOG_ERROR("Execl shell cmd(%s) fail,iRet(%ld) iScriptRet(%ld).",
                              pcCmd, iRet, iScriptRet);
                    return RET_ERR;
                }
                return iRet;
            }
        }

        /*****************************************************************************
         * 函 数 名  : OS_WaitChild
         * 负 责 人  : 卢美宏
         * 创建日期  : 2018年4月1日
         * 函数功能  : 等待子进程执行退出，带超时机制，同时可以获知脚本执行输出结果
         * 输入参数  : pid_t uiChildPid  子线程id
                     int *piFd         文件描述符
                    UINT uiTimeout     等待子线程最大执行时间
                    STATUS *iScriptRet   子线程退出状态
                    char *pOutBuf     子线程输出缓存
                    UINT64 uiOutBufLen   缓存大小
        * 输出参数  : 无
        * 返 回 值  : 执行结果
        * 调用关系  :
        * 其    它  :

        *****************************************************************************/
        STATUS OS_WaitChild(pid_t uiChildPid, int iFd, UINT uiTimeout,
                            STATUS *iScriptRet, char *pOutBuf, UINT64 uiOutBufLen)
        {
            STATUS iRet = RET_ERR;
            STATUS iStat = 0;
            UINT64 uiBeginTime = 0;
            UINT64 uiEndTime = 0;
            UINT64 uiTime = 0;
            UINT64 iOffset = 0;
            pid_t iStopPid = 0;

            uiTime = (uiTimeout == 0) ? INVALUE_INAVLE : uiTimeout * 1000;

            uiBeginTime = GetCurrentMillisecs();

            for (;;)
            {

                if ((0 < iFd) && (NULL != pOutBuf))
                {
                    iOffset += OS_CheckReadBuf(iFd, pOutBuf + iOffset,
                                               uiOutBufLen - iOffset);
                }

                iStopPid = wait4(uiChildPid, (int *)&iStat, WNOHANG, 0);
                uiEndTime = GetCurrentMillisecs();

                //wait4异常 或者等待超时
                if ((0 != iStopPid) || ((uiEndTime - uiBeginTime) > uiTime))
                {
                    break;
                }
                usleep(200);
            }

            if (0 > iStopPid)
            {
                iRet = OS_Kill(uiChildPid);
                LOG_WARN("wait child exit fail,iRet(%ld) .", iRet);
                return RET_INDIDE_ERR;
            }
            else if (0 == iStopPid)
            {
                iRet = OS_Kill(uiChildPid);
                LOG_WARN("Wait Child Timeout(%llu) but used(%llu),iRet(%ld).",
                         uiTime, uiEndTime - uiBeginTime, iRet);
                return RET_TIMEOUT;
            }
            else if (iStopPid == uiChildPid)
            {
                return OS_GetExitStatus(iStat, iScriptRet);
            }
            else
            {
                return RET_ERR;
            }
        }

        /*****************************************************************************
         * 函 数 名  : OS_CheckReadBuf
         * 负 责 人  : 卢美宏
         * 创建日期  : 2018年4月1日
         * 函数功能  : 通过句柄变化获知是否可以读数据
         * 输入参数  : STATUS v_uiFd       文件描述符
                     char *pOutBuf    输出缓存地址
                    UINT uiOutBufLen  输出缓存的大小
        * 输出参数  : 无
        * 返 回 值  : 执行结果
        * 调用关系  :
        * 其    它  :

        *****************************************************************************/
        LONG OS_CheckReadBuf(int v_uiFd, char *pOutBuf, UINT64 uiOutBufLen)
        {
            STATUS iRet = RET_ERR;
            LONG iReadLen = 0;
            struct timeval tv;
            fd_set fdset;
            tv.tv_usec = TV_USEC_VALUE;
            tv.tv_sec = TV_USEC_VALUE / 1000;

            if (NULL == pOutBuf || uiOutBufLen == 0)
                return 0;

            FD_ZERO(&fdset);
            FD_SET(v_uiFd, &fdset);

            iRet = select(v_uiFd + 1, &fdset, NULL, NULL, &tv);

            if ((0 >= iRet) ||             // -1:timeout 0:select error
                !FD_ISSET(v_uiFd, &fdset)) //read is true
            {
                // LOG_WARN("Select function execl fail(%d).", iRet);
                return 0;
            }

            iReadLen = read(v_uiFd, pOutBuf, uiOutBufLen);

            return (iReadLen ? iReadLen : 0);
        }

        /*****************************************************************************
         * 函 数 名  : OS_Kill
         * 负 责 人  : 卢美宏
         * 创建日期  : 2018年6月16日
         * 函数功能  : kill指定的进程
         * 输入参数  : pid_t uiChildPid  需要杀死的进程id
         * 输出参数  : 无
         * 返 回 值  : 执行结果
         * 调用关系  :
         * 其    它  :

        *****************************************************************************/
        STATUS OS_Kill(pid_t uiChildPid)
        {
            STATUS iRet = RET_ERR;
            pid_t tKillPid = 0;
            char sChildStat = 0;

            (void)killpg(uiChildPid, SIGTERM);
            usleep(200);
            (void)killpg(uiChildPid, SIGKILL);

            tKillPid = wait4(uiChildPid, NULL, WNOHANG, 0);

            if (tKillPid == uiChildPid)
                return RET_OK;

            iRet = OS_GetProcessStatus(uiChildPid, &sChildStat);
            if (RET_OK != iRet)
            {
                tKillPid = wait4(uiChildPid, NULL, WNOHANG, 0);
                if (tKillPid == uiChildPid)
                {
                    return RET_OK;
                }
                else
                {
                    LOG_WARN("kill childPid(%d) fail.", uiChildPid);
                    return RET_INDIDE_ERR;
                }
            }
            else if ('D' != sChildStat)
            {
                return RET_STAT_D;
            }
            else
            {
                (void)wait4(uiChildPid, NULL, 0, 0);
                return RET_OK;
            }
        }

        /*****************************************************************************
         * 函 数 名  : OS_GetProcessStatus
         * 负 责 人  : 卢美宏
         * 创建日期  : 2018年4月1日
         * 函数功能  : 获取子进程退出的状态D
         * 输入参数  : pid_t uiPid     子线程pid
                     char *v_Status  输出子线程执行状态
        * 输出参数  : 无
        * 返 回 值  : 执行结果
        * 调用关系  :
        * 其    它  :

        *****************************************************************************/
        STATUS OS_GetProcessStatus(pid_t uiPid, char *v_Status)
        {
            LONG iRet = RET_ERR;
            STATUS iFd = RET_ERR;
            char acStatPath[STAT_PATH_LEN] = {0};
            char acStatBuffer[STAT_BUFFER_LEN] = {0};
            char *pcEedBracket = NULL;

            if (NULL == v_Status)
                return RET_ERR;

            iRet = snprintf(acStatPath, STAT_PATH_LEN - 1, "/proc/%d/stat", uiPid);
            if (RET_ERR == iRet)
                return RET_INDIDE_ERR;

            iFd = open(acStatPath, O_RDONLY, NULL);

            if (0 >= iFd)
                return RET_INDIDE_ERR;

            iRet = read(iFd, acStatBuffer, STAT_BUFFER_LEN - 1);
            close(iFd);

            if (0 == iRet)
                return RET_INDIDE_ERR;

            pcEedBracket = strstr(acStatBuffer, ")");
            if (NULL == pcEedBracket)
                return RET_INDIDE_ERR;

            *v_Status = *(pcEedBracket + 2);
            return RET_OK;
        }

        /*****************************************************************************
         * 函 数 名  : OS_GetExitStatus
         * 负 责 人  : 卢美宏
         * 创建日期  : 2018年4月1日
         * 函数功能  :  获取子进程退出的的状态码
         * 输入参数  : STATUS iStatus       线程退出状态
                     STATUS *v_ScriptRet  输出状态
        * 输出参数  : 无
        * 返 回 值  : 执行结果
        * 调用关系  :
        * 其    它  :

        *****************************************************************************/
        STATUS OS_GetExitStatus(STATUS iStatus, STATUS *v_ScriptRet)
        {
            if (NULL == v_ScriptRet)
                return RET_ERR;

            //正常退出
            if (WIFEXITED(iStatus))
            {
                *v_ScriptRet = WEXITSTATUS(iStatus);
                return RET_OK;
            }

            if (WIFSIGNALED(iStatus))
            {
                LOG_WARN("Get pid exit status fail.");
            }

            return RET_EXCEPTIONAL;
        }
        /*****************************************************************************
         * 函 数 名  : OS_GetStrValueByCmd
         * 负 责 人  : 卢美宏
         * 创建日期  : 2018年4月1日
         * 函数功能  : 获取脚本执行后输出结果
         * 输入参数  : const char * pacCmd  待执行命令
                     char *pBuffer        命令执行输出结果缓存地址
                    UINT64 uiBufferLen      缓存地址大小限制
        * 输出参数  : 无
        * 返 回 值  :
        * 调用关系  :
        * 其    它  :

        *****************************************************************************/
        STATUS OS_GetStrValueByCmd(const char *pacCmd, char *pBuffer, UINT64 uiBufferLen)
        {
            STATUS iRet = RET_ERR;

            if (NULL == pacCmd || NULL == pBuffer || (0 >= uiBufferLen))
                return RET_ERR;

            (void)memset(pBuffer, '\0', uiBufferLen);
            iRet = OS_ReadBufByCmd(pacCmd, OM_CMD_EXCE_TIME, pBuffer, uiBufferLen);

            if (strlen(pBuffer) == 0)
                return RET_ERR;

            return iRet;
        }

        /*****************************************************************************
         * 函 数 名  : OS_ReadBufByCmd
         * 负 责 人  : 卢美宏
         * 创建日期  : 2018年4月1日
         * 函数功能  :  读取buffer中数据
         * 输入参数  : const char * pacCmd  待执行命令
                     UINT uiTimeout        命令执行超时限制
                    char *pBuffer        命令输出结果缓存地址
                    UINT64 uiBufferLen      缓存地址大小限制
        * 输出参数  : 无
        * 返 回 值  : 执行结果
        * 调用关系  :
        * 其    它  :

        *****************************************************************************/
        STATUS OS_ReadBufByCmd(const char *pacCmd, UINT uiTimeout,
                               char *pBuffer, UINT64 uiBufferLen)
        {

            STATUS iRet = RET_ERR;
            STATUS iScriptRet = RET_ERR;
            pid_t iChildPid = 0;
            UINT uiFileIndex = 0;
            int pdes[2] = {0};
            struct rlimit stFileLimit;

            if (0 > pipe(pdes))
            {
                LOG_ERROR("Creat pipe fail.");
                return RET_ERR;
            }

            if (0 > (iChildPid = vfork()))
            {
                close(pdes[0]);
                close(pdes[1]);
                LOG_ERROR("Creat Child fail.");
                return RET_ERR;
            }

            if (iChildPid == 0)
            {
                //恢复三种信号源
                (void)signal(SIGQUIT, SIG_DFL);
                (void)signal(SIGCHLD, SIG_DFL);
                (void)signal(SIGINT, SIG_DFL);
                setpgid(0, 0); //设置组便于一次kill
                //获取进程能打开最大文件数并关闭
                if (0 == getrlimit(RLIMIT_NOFILE, &stFileLimit))
                {
                    for (uiFileIndex = STDOUT_FILENOED + 1;
                         uiFileIndex < stFileLimit.rlim_max;
                         ++uiFileIndex)
                    {
                        //关闭子进程的非写端
                        if ((int)uiFileIndex != pdes[1])
                            close((int32_t)uiFileIndex);
                    }
                    dup2(pdes[1], STDOUT_FILENO);
                    close(pdes[1]);
                    (void)execl("/bin/sh", "sh", "-c", pacCmd, (char *)0);
                }
                _exit(127);
            }
            else
            {
                close(pdes[1]);
                iRet = OS_WaitChild(iChildPid, pdes[0], uiTimeout,
                                    &iScriptRet, pBuffer, uiBufferLen);
                close(pdes[0]);
                if ((RET_OK != iRet) || (RET_OK != iScriptRet))
                {
                    LOG_ERROR(
                        "Execl shell cmd(%s) fail,iRet(%ld) iScriptRet(%ld).",
                        pacCmd, iRet, iScriptRet);
                    return RET_ERR;
                }
                return RET_OK;
            }
        }
#endif
#ifdef __cplusplus
    }
#endif

    /*****************************************************************************
     * 函 数 名  : getCurrentTime
     * 负 责 人  : 卢美宏
     * 创建日期  : 2021年9月28日
     * 函数功能  :  获取时间格式化后字符串
     * 输入参数  : N/A
    * 输出参数  : 无
    * 返 回 值  : 执行结果
    * 调用关系  :
    * 其    它  :

    *****************************************************************************/
    std::string getCurrentTime()
    {
        std::string _Str(TIME_FROMAT_BUFFER_LEN, '\0');
        (void)getCurrentTime(&_Str[0], TIME_FROMAT_BUFFER_LEN);
        return _Str;
    }
}